/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://github.com/payara/Payara/blob/main/LICENSE.txt
 * See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at legal/OPEN-SOURCE-LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2018] [Payara Foundation]

//----------------------------------------------------------------------------
//
// Module:      LogHandle.java
//
// Description: Log file handle.
//
// Product:     com.sun.jts.CosTransactions
//
// Author:      Simon Holdsworth
//
// Date:        March, 1997
//
// Copyright (c):   1995-1997 IBM Corp.
//
//   The source code for this program is not published or otherwise divested
//   of its trade secrets, irrespective of what has been deposited with the
//   U.S. Copyright Office.
//
//   This software contains confidential and proprietary information of
//   IBM Corp.
//----------------------------------------------------------------------------

package com.sun.jts.CosTransactions;

// Import required classes.

import com.sun.enterprise.util.i18n.StringManager;

import java.security.PrivilegedAction;
import java.util.*;
import java.io.*;

/**A class containing attributes of an open log file.
 *
 * @version 0.01
 *
 * @author Simon Holdsworth, IBM Corporation
 *
 * @see
*/
//----------------------------------------------------------------------------
// CHANGE HISTORY
//
// Version By     Change Description
//   0.01  SAJH   Initial implementation.
//-----------------------------------------------------------------------------

class LogHandle {
    private static final StringManager sm = StringManager.getManager(LogHandle.class);

    // WriteMode identifies the mode in which a system journal record
    // is to be written, and affects the performance overhead of the write.

    /**Buffer the data and return (minimal overhead);
     */
    final static int BUFFER = 0;

    /**Flush and force the data to permanent storage before returning (high
     * overhead) - ie to physically write the record.
     */
    final static int FORCE = 1;

    // This type enumerates the options when truncating a log file.

    /**Don't include tail LSN
     */
    final static int TAIL_NOT_INCLUSIVE = 0;

    /**Include tail LSN
     */
    final static int TAIL_INCLUSIVE = 1;

    // Records written to the Master Log are allocated log record types

    /**Start-of-checkpoint record - internally generated by &damjo.
     */
    final static int START_CKPT = 0;

    /**Checkpoint record from an individual registered module
     */
    final static int INDV_CKPT = 1;

    /**End-of-checkpoint record - internally generated by &damjo
     */
    final static int END_CKPT = 2;

    /**Record of a newly opened journal
     */
    final static int NEW_JRNL = 3;

    /**Upper limit for user specified record type value.
     */
    final static int RECORD_TYPE_MAX = 0xFFFF;

    /**Record type written to local system logs to indicate the position
     * corresponding to the start of the last successful checkpoint.
     */
    final static int MARKER = RECORD_TYPE_MAX;

    /**The record type written to at the end of an extent to signify that the
     * log record is a link record (a dummy log record).
     */
    final static int LINK = RECORD_TYPE_MAX + 1;

    // Constants used for log files.

    /**The maximum number of extents which can be created for a log file.
     */
    final static int MAX_NUMBER_EXTENTS = 0xFFFFFFFF;

    /**Number of log write operations which will be performed before forcing
     * the control data to permanent storage
     */
    // final static int CONTROL_FORCE_INTERVAL = 20;
    final static int CONTROL_FORCE_INTERVAL = 100;

    /**This determines the size of the largest log record which can be written.
     */
    final static int MAX_EXTENT_SIZE = LogFileHandle.FILESYSTEM_BLOCKSIZE*16;

    /**This is the size of the cushion file used to find if the log is
     * short on space.
     */
    final static int CUSHION_SIZE = MAX_EXTENT_SIZE;

    /**The length of the name assigned to a logfile. This is restricted to
     * 8 to support the FAT file system.
     */
    final static int NAME_LENGTH = 8;

    /**The maximum number of names available to be assigned for logfiles.
     * The name is made up of LOG_FILENAME_PREFIX which is 5 characters
     * followed by a 3 digit hex extension.
     */
    final static int MAX_NAMES = 4096;

    /**The length of the fixed filename prefix used when allocating new
     * log file names.
     */
    final static int FILENAME_PREFIX_LEN = 5;

    /**The number of entries in each log file descriptor. It is used for
     * performance reason, so we can get to the extent descriptor quickly.
     */
    final static int EXTENT_TABLE_SIZE = 16;

    /**This is used to give the maximum length of a log file name, which also
     * inclues the NULL terminator. 200 for the logname was derived from :
     *   /var/cics_regions/region_name/log/<logname>.extent.00000001
     * We know the maximum region name is 8 chars, therefore every character
     * except the logname added upto 46. Hence 255 (AIX path max) - 46 is 209,
     * however 200 is a nice round (and large number).
     */
    //final static int NAME_MAX_SIZE = 200;

    /**This is the reason why we are calling the calling back function.
     */
    final static int CALLBACK_REASON_SOS = 1;

    /**The offset in the control file for the first restart data record.
     */
    final static int RESTART_OFFSET_1 = LogFileHandle.FILESYSTEM_BLOCKSIZE;

    /**The offset in the control file for the second restart data record.
     */
    final static int RESTART_OFFSET_2 = LogFileHandle.FILESYSTEM_BLOCKSIZE*5;

    /**This is the maximum size of a log record.
     */
    final static int MAX_RECORD_SIZE = MAX_EXTENT_SIZE - 2*LogRecordHeader.SIZEOF - 2*LogRecordEnding.SIZEOF;

    /**The maximum size of a restart record
     */
    final static int MAX_RESTART_SIZE = LogFileHandle.FILESYSTEM_BLOCKSIZE*4 - 2*LogRestartDescriptor.SIZEOF;

    /**The size of a control file which is allocated at open time.
     */
    final static int CONTROL_FILE_SIZE = RESTART_OFFSET_2 + MAX_RESTART_SIZE + 2*LogRestartDescriptor.SIZEOF;

    /**The size of a chunk to allocate from the disk space
     */
    final static int ALLOCATE_SIZE = MAX_EXTENT_SIZE;

    // Instance members

    LogHandle            blockValid = null;
    int                  restartDataLength = 0;
    int                  recordsWritten = 0;
    int                  chunkRemaining = 0;
    int                  activeRestartVersion = 0;
    LogUpcallTarget      upcallTarget = null;
    ArrayList            cursors = null;
    boolean              cushionExists = false;
    boolean              upcallInProgress = false;
    Hashtable            extentTable = null;
    String               logFileName = null;
    LogFileHandle        logFileHandle = null;
    LogControlDescriptor logControlDescriptor = null;
    LogControl           logControl = null;


    /**Creates a LogHandle object for the given log instance.
     *
     * @param control    The log instance.
     * @param logName    The name of the log.
     * @param controlFH  The handle of the control file.
     * @param upcall     The log upcall.
     *
     * @return
     *
     * @exception LogException The creation failed.
     *
     * @see
     */
    LogHandle( LogControl      control,
               String          logName,
               LogFileHandle   controlFH,
               LogUpcallTarget upcall )
        throws LogException {

        // Initialise instance members.

        logFileName = logName;
        upcallTarget = upcall;
        logControl = control;
        logFileHandle = controlFH;
        blockValid = this;
        logControlDescriptor = new LogControlDescriptor();
        cursors = new ArrayList();
        extentTable = new Hashtable(EXTENT_TABLE_SIZE);

    }

    /**Writes a record to the log.
     *
     * @param record      The log record.
     * @param recordType  The log record type.
     * @param writeMode   The write mode.
     *
     * @return  The LSN of the written record
     *
     * @exception LogException The write failed.
     *
     * @see
     */
    synchronized LogLSN writeRecord( byte[] record,
                                     int    recordType,
                                     int    writeMode )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // IF ReadOnly log
        //   Return LOG_READ_ONLY_ACCESS

        if( logControl.logReadOnly )
            throw new LogException(null,LogException.LOG_READ_ONLY_ACCESS,3);

        // Sanity check the recordType and writeMode parameters

        if( recordType > RECORD_TYPE_MAX )
            throw new LogException(null,LogException.LOG_INVALID_RECORDTYPE,5);

        if( writeMode != FORCE && writeMode != BUFFER )
            throw new LogException(null,LogException.LOG_INVALID_WRITEMODE,6);

        // Calculate the total size of the log record by totalling size of all
        // input buffers together with the record header and record ending

        int recordSize = record.length + LogRecordHeader.SIZEOF + LogRecordEnding.SIZEOF;


        // IF the log record data is greater than LOG_MAX_LOG_RECORD_SIZE
        //   Unlock the log file latch
        //   Return LOG_RECORD_TOO_LARGE

        if( recordSize > MAX_RECORD_SIZE )
            throw new LogException(null,LogException.LOG_RECORD_TOO_LARGE,7);

        // Calculate the remaining space in the current extent by subtracting
        // (log head LSN's offset + 2*LOG_HEADER_SIZE + LOG_ENDING_SIZE) from
        // LOG_MAX_EXTENT_SIZE

        int remainingSpace = MAX_EXTENT_SIZE - ( logControlDescriptor.nextLSN.offset
                                                 + 2*LogRecordHeader.SIZEOF
                                                 + LogRecordEnding.SIZEOF );


        // Position the file pointer to the next free location
        // NOTE: either the record or a link record will be wrote here
        // Set the WORKING extent descriptor to the returned value
        //
        // IF an error occurs let it go to the caller.

        LogExtent logEDP = positionFilePointer(logControlDescriptor.nextLSN,0,LogExtent.ACCESSTYPE_WRITE);

        // IF not enough space in current extent

        if( remainingSpace < recordSize ) {
            LogRecordHeader link = new LogRecordHeader();

            // Calculate the number of the next (new) extent
            // Calculate LSN of first record in the new extent.
            // Test that the new extent number has not wrapped to become negative;
            //   if it has, throw an exception.

            int nextExtent = logControlDescriptor.headLSN.extent+1;
            if( nextExtent < 0 )
                throw new LogException(null,LogException.LOG_WRITE_FAILURE,8);

            // If the new extent file is already open, there is nothing we can do but
            // fail.  We cannot run the short-on-storage upcall to try to free the
            // extent as the upcall needs to write information to the offending extent.

            if( extentTable.containsKey(LogExtent.modExtent(nextExtent)) )
                throw new LogException(null,LogException.LOG_WRITE_FAILURE,9);

            // Create link record containing
            // - the LSN of the link record (i.e. its own LSN)
            // - the LSN of the previous log record (log head LSN from
            //   Log_FileDescriptor block)
            // - the LSN of the next log record (this is the LSN of the
            //   first record in new extent file

            link.recordType = LINK;
            link.previousLSN = new LogLSN(logControlDescriptor.headLSN);
            link.currentLSN  = new LogLSN(logControlDescriptor.nextLSN);
            link.nextLSN     = new LogLSN(nextExtent,0);

            // Move a file pointer to the next record position

            LogExtent nextEDP = positionFilePointer(link.nextLSN,0,LogExtent.ACCESSTYPE_WRITE);

            // Issue WRITE to add link record to the 'full' extent file
            // IF the WRITE fails
            //   Close the new extent file
            //   Unchain its extent descriptor block from the hash table
            //   Deallocate the extent descriptor block
            //   Unlock the log file latch
            //   Return LOG_WRITE_FAILURE

            byte[] linkBytes = new byte[link.SIZEOF];
            link.toBytes(linkBytes,0);
            int bytesWritten = 0;
            try {
                bytesWritten = logEDP.fileHandle.fileWrite(linkBytes);
            } catch( LogException le ) {
                extentTable.remove(logControlDescriptor.headLSN.extent);
                nextEDP.doFinalize();
                throw new LogException(LogException.LOG_WRITE_FAILURE, 10,
                        sm.getString("jts.log_add_link_failed"), le);
            }

            // Set its 'extent written' flag to TRUE

            logEDP.writtenSinceLastForce = true;
            logEDP.cursorPosition += bytesWritten;

            // Update the head LSN value in the Log_FileDescriptor block
            // with the LSN of the link record
            // Update the next LSN value in the Log_FileDescriptor block
            // with the LSN of the first block in the new extent

            logControlDescriptor.headLSN.copy(link.currentLSN);
            logControlDescriptor.nextLSN.copy(link.nextLSN);

            // Set the WORKING extent descriptor to the new/next extent

            logEDP = nextEDP;

            // Set the ChunkRemaining to Zero

            chunkRemaining = 0;
        }

        // Use the offset value from the next LSN to calculate the next free offset
        // in the extent file
        // Calculate the 'next free' LSN

        LogLSN nextFree = new LogLSN(logControlDescriptor.nextLSN.extent,
                                     logControlDescriptor.nextLSN.offset + recordSize);

        // Build the record header, initialising with
        // - log record type (recordType passed as input parameter)
        // - log record length (cumulative length of all data buffers)
        // - the LSN of the previous log record (PreviousRecord; log head LSN from
        //   Log_FileDescriptor block)
        // - the LSN of the next log record (NextRecord; the 'next free' LSN value)
        // - the LSN of the record about to be written (ThisRecord)

        LogRecordHeader logRH = new LogRecordHeader();

        logRH.recordType   = recordType;
        logRH.recordLength = record.length;
        logRH.nextLSN      = nextFree;
        logRH.previousLSN  = new LogLSN(logControlDescriptor.headLSN);
        logRH.currentLSN   = new LogLSN(logControlDescriptor.nextLSN);

        // Build the record ending, initialising with
        // the LSN of the record about to be written (ThisRecord)

        LogRecordEnding logRE = new LogRecordEnding();

        logRE.currentLSN = logRH.currentLSN;


        // Initialise an array of iovec structures ready for a WRITEV request
        // (an iovec structure specifies the base address and length of an area in
        // memory from which data should be written)
        // - set the first element to point to the record header, set iovCount=1
        // - LOOP for each buffer in recordPtrList
        //   initialise next iovec element with its address and length
        //   increment iovCount
        //   ENDLOOP
        // - set the next element to point to the record ending, increment iovCount

        byte[] writeBytes = new byte[LogRecordHeader.SIZEOF+record.length+LogRecordEnding.SIZEOF];

        logRH.toBytes(writeBytes,0);
        System.arraycopy(record,0,writeBytes,LogRecordHeader.SIZEOF,record.length);
        logRE.toBytes(writeBytes,LogRecordHeader.SIZEOF+record.length);

        // IF there is enough space in current chunk
        //   Decrease ChunkRemaining by RecordSize

        boolean cushionFreed = false;

        if( chunkRemaining > recordSize )
            chunkRemaining -= recordSize;
        else {

            // CALCULATE the size of disk space to grab

            int grabSize = chunkRemaining + ALLOCATE_SIZE;

            // IF there is NOT enough space in current extent
            //   Set the Grab size to be the size of the remaining extent

            if( grabSize + logControlDescriptor.nextLSN.offset > MAX_EXTENT_SIZE )
                grabSize = MAX_EXTENT_SIZE - logControlDescriptor.nextLSN.offset;


            // Set the Allocate success flag to FALSE;

            boolean allocateSuccess = false;

            do {
                // ALLOCATE the Grab size of disk space
                // IF successful
                //   Set AllocateSuccess to TRUE
                //   BREAK

                try {
                    logEDP.fileHandle.allocFileStorage(grabSize);
                } catch( LogException le ) {

                    // IF the request fails due to lack of storage, i.e.
                    //      ENOSPC - insufficient space left in file system or
                    //      EDQUOT - user or group disk block quota reached
                    //   Call the Log_FreeCushion routine
                    //   IF there was no cushion to free
                    //     Unlock the log file latch
                    //     Return LOG_NO_SPACE
                    //   Move the File pointer back to it's original offset
                    // ELSE
                    //   EXIT LOOP with 'Allocate unsuccessful' status

                    if( le.errorCode == LogException.LOG_NO_SPACE ) {
                        if( cushionExists ) {
                            freeCushion();
                            cushionFreed = true;
                        } else {
                            if( cushionFreed )
                                restoreCushion(false);

                            throw new LogException(LogException.LOG_NO_SPACE,11, null, le);
                        }

                        try {
                            logEDP = positionFilePointer(logControlDescriptor.nextLSN,0,LogExtent.ACCESSTYPE_WRITE);
                        } catch( Throwable e ) {};
                    }
                    else
                        allocateSuccess = false;
                }
                allocateSuccess = true;
            }
            while( !allocateSuccess );
            // SET ChunkRemaining to the Grabbed size - RecordSize

            chunkRemaining = grabSize - recordSize;
        }

        // Issue a WRITEV request to the extent file, specifying the iovec array
        // and iovCount as input
        // IF write failed return the error.

        int bytesWritten = logEDP.fileHandle.fileWrite(writeBytes);

        // Set 'extent written' flag to TRUE

        logEDP.writtenSinceLastForce = true;
        logEDP.cursorPosition += bytesWritten;

        // IF LOG_FORCE was specified
        //   LOOP through each extent chain in the hash table
        //     IF 'extent written' flag is TRUE
        //       Issue FSYNC for extent file descriptor
        //       IF not successful
        //         Unlock the log file latch
        //         Return LOG_ERROR_FORCING_LOG
        //       Set 'extent written' flag to FALSE
        //   ENDLOOP

        if( writeMode == FORCE ) {
            Enumeration extents = extentTable.elements();
            while( extents.hasMoreElements() ) {
                LogExtent nextEDP = (LogExtent)extents.nextElement();
                if( nextEDP.writtenSinceLastForce )
                    try {
                        nextEDP.fileHandle.fileSync();
                        nextEDP.writtenSinceLastForce = false;
                    } catch (LogException le) {
                        throw new LogException(LogException.LOG_ERROR_FORCING_LOG, 14,
                                sm.getString("jts.log_file_sync_failed"), le);
                    }
            }
        }

        // Update the head LSN and 'next free' LSN in the Log_FileDescriptor
        // block

        logControlDescriptor.headLSN.copy(logRH.currentLSN);
        logControlDescriptor.nextLSN.copy(logRH.nextLSN);

        // Increment the RecordsWritten counter in Log_FileDescriptor block

        recordsWritten++;

        // IF RecordsWritten = LOG_CONTROL_FORCE_INTERVAL or LOG_FORCE was specified
        //   Write the Log_ControlDescriptor structure (embedded in the
        //   Log_FileDescriptor block out to the control file (implied sync)
        //   IF not successful let the error pass to the caller.
        //   Reset the RecordsWritten counter to zero
        //   IF LogCushionOK is FALSE
        //     Call RestoreLogCushion Routine

        if( recordsWritten >= CONTROL_FORCE_INTERVAL ) {
            writeControlFile();
            recordsWritten = 0;
        }

        if( cushionFreed )
            restoreCushion(true);

        // Return the written LSN as the result of the write operation.

        LogLSN result = new LogLSN(logRH.currentLSN);


        return result;
    }

    /**Reads a record from the log.
     *
     * @param readLSN  The LSN of the record to be read.
     * @param type     An array with a single element which will be set to the type
     *                 of the record read.
     *
     * @return  The record read in.
     *
     * @exception LogException  The read failed.
     *
     * @see
     */
    synchronized byte[] readRecord( LogLSN readLSN,
                                    int[/*1*/] type )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // IF the log file is empty (head LSN equal to LOG_NULL_LSN)
        //   Unlock the log file latch
        //   Return LOG_INVALID_LSN

        if( logControlDescriptor.headLSN.isNULL() )
            throw new LogException(null,LogException.LOG_INVALID_LSN,3);

        // IF the lsn specified is LOG_HEAD_LSN or LOG_TAIL_LSN
        //   substitute the current head or tail LSN from the
        //   Log_ControlDescriptor structure
        // ELSE
        //   Ensure that the lsn specified is <= current head LSN and
        //                                    >= current tail LSN
        //   IF lsn does not pass these checks
        //     Unlock the log file latch
        //     Return LOG_INVALID_LSN

        LogLSN lsn;

        if( readLSN.equals(LogLSN.HEAD_LSN) )
            lsn = logControlDescriptor.headLSN;
        else if( readLSN.equals(LogLSN.TAIL_LSN) )
            lsn = logControlDescriptor.tailLSN;
        else if( readLSN.lessThan(logControlDescriptor.tailLSN) ||
                 readLSN.greaterThan(logControlDescriptor.headLSN) )
            throw new LogException(null,LogException.LOG_INVALID_LSN,4);
        else
            lsn = readLSN;

        // Position the file pointer to the LSN specified
        // IF not successful allow the error to pass to the caller.

        LogExtent logEDP = positionFilePointer(lsn,0,LogExtent.ACCESSTYPE_READ);

        // Issue a READ for the log header record
        // IF the READ was not successful
        //   Unlock the log file latch
        //   Return LOG_READ_FAILURE

        byte[] headerBytes = new byte[LogRecordHeader.SIZEOF];
        int bytesRead = 0;
        try {
            bytesRead = logEDP.fileHandle.fileRead(headerBytes);
        } catch (LogException le) {
            logEDP.lastAccess = LogExtent.ACCESSTYPE_UNKNOWN;
            throw new LogException(le.errorCode, 6,
                    sm.getString("jts.log_read_header_failed"), le);
        }

        LogRecordHeader logRH = new LogRecordHeader(headerBytes,0);

        logEDP.cursorPosition += bytesRead;

        // Check the record type is not a LOG_LINK_RECORD_TYPE &&
        // the LSN in the header record is same as lsn parameter
        // IF either test fails
        //   Unlock the log file latch
        //   Return LOG_INVALID_LSN

        if( logRH.recordType == LINK ||
            !logRH.currentLSN.equals(lsn)  )
            throw new LogException(null,LogException.LOG_INVALID_LSN,7);

        // Set up a 2-element iovec array to enable the log record data and record
        // ending to be read into a separate buffers
        // Issue a READV request for the extent file, passing the iovec array as
        // an input parameter
        // IF the READV was not successful
        //   Unlock the log file latch
        //   Return LOG_READ_FAILURE

        byte[][] readVect = new byte[2][];
        readVect[0] = new byte[logRH.recordLength];
        readVect[1] = new byte[LogRecordEnding.SIZEOF];

        try {
            bytesRead = logEDP.fileHandle.readVector(readVect);
        } catch( LogException le ) {
            logEDP.lastAccess = LogExtent.ACCESSTYPE_UNKNOWN;
            throw new LogException(le.errorCode,9, sm.getString("jts.log_readvector_failed"), le);
        }

        LogRecordEnding logRE = new LogRecordEnding(readVect[1],0);
        logEDP.cursorPosition += bytesRead;

        // IF the LSN contained in the record ending != lsn parameter
        //   Unlock the log file latch
        //   Return LOG_CORRUPTED

        if( !logRE.currentLSN.equals(lsn) )
            throw new LogException(null,LogException.LOG_CORRUPTED,10);

        // Copy the returned number of bytes into the recordLengthP parameter and
        // the record type value into the recordTypeP parameter.

        type[0] = logRH.recordType;

        return readVect[0];
    }

    /**Writes the restart record.
     *
     * @param buffer The record to be written.
     *
     * @return
     *
     * @exception LogException The write failed.
     *
     * @see
     */
    synchronized void writeRestart( byte[] buffer )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // IF ReadOnly log
        //   Return LOG_READ_ONLY_ACCESS

        if( logControl.logReadOnly )
            throw new LogException(null,LogException.LOG_READ_ONLY_ACCESS,3);

        // IF the bufferLength parameter is greater than LOG_MAX_RESTART_RECORD_SIZE
        //   Return LOG_RECORD_TOO_LARGE

        if( buffer.length > MAX_RESTART_SIZE )
            throw new LogException(null,LogException.LOG_RECORD_TOO_LARGE,4);

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is still valid

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,5);

        // Use the value in ActiveRestartVersion field showing which is the active
        // to determine which is the alternate restart record
        // Use LSEEK to move the file pointer to its offset

        int alternate = alternateRestart(activeRestartVersion);
        int restartOffset = restartPosition(alternate);
        logFileHandle.fileSeek(restartOffset,LogFileHandle.SEEK_ABSOLUTE);

        // Initialise a Log_RestartDescriptor block with
        // - the current file pointer offset (copied into RestartValid field)
        // - the length of the restart data (DataLength field)
        // - a timestamp obtained from the seconds field of a gettimer call

        LogRestartDescriptor logRD = new LogRestartDescriptor();

        logRD.restartDataLength = buffer.length;
        logRD.timeStamp = (int)new Date().getTime();
        logRD.restartValid = restartOffset;


        // Set up a 3-element iovec array with the first element 'containing'
        // the Log_RestartDescriptor block, the second, the supplied
        // restart data and the third, the Log_RestartDescriptor block again.

        byte[] writeBytes = new byte[LogRestartDescriptor.SIZEOF*2+buffer.length];

        logRD.toBytes(writeBytes,0);
        System.arraycopy(buffer,0,writeBytes,LogRestartDescriptor.SIZEOF,buffer.length);
        logRD.toBytes(writeBytes,LogRestartDescriptor.SIZEOF+buffer.length);

        // Issue a WRITEV request to copy the restart data to the control file
        // IF successful
        //   Data has now been written to permanent storage, so update
        //   RestartDataLength field in Log_FileDescriptor with bufferLength
        //   and indicate (value 1 or 2) in ActiveRestartVersion field that the
        //   alternate has now become the active
        //   Return LOG_SUCCESS
        // ELSE let the error pass to the caller.

        logFileHandle.fileWrite(writeBytes);

        activeRestartVersion = alternate;

    }

    /**Reads the restart record.
     *
     * @param
     *
     * @return  The record read in.
     *
     * @exception LogException The read failed.
     *
     * @see
     */
    synchronized byte[] readRestart()
        throws LogException {
        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is still valid

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,3);

        // IF there is no restart data (restart length in Log_FileDescriptor
        //   block is zero)
        //   Return LOG_NO_RESTART_RECORD

        if( restartDataLength == 0 )
            return new byte[0];

        // Use the ActiveRestartVersion field in the Log_FileDescriptor block
        // to find out which restart record is currently the active one and
        // determine its offset within the control file
        // Use LSEEK to move the file pointer to the start of the restart record
        // Allow any error to pass to the caller.

        int restartOffset = restartPosition(activeRestartVersion);
        logFileHandle.fileSeek(restartOffset,LogFileHandle.SEEK_ABSOLUTE);

        // Initialise an iovec array with the first element containing details of
        // a Log_RestartDescriptor block, the second containing details of
        // the callers buffer (bufferP and restart data length) and the third also
        // pointing to a Log_RestartDescriptor block

        byte[][] readVect = new byte[3][];

        readVect[0] = new byte[LogRestartDescriptor.SIZEOF];
        readVect[1] = new byte[restartDataLength];
        readVect[2] = new byte[LogRestartDescriptor.SIZEOF];

        // Issue a READV for the restart data
        // IF not successful let the error pass to the caller.

        logFileHandle.readVector(readVect);

        LogRestartDescriptor logRD    = new LogRestartDescriptor(readVect[0],0);
        LogRestartDescriptor logRDEnd = new LogRestartDescriptor(readVect[2],0);

        // IF the offset value stored in the returned Log_RestartDescriptor
        // block is not equal to the offset of the record just read OR
        // the length held in the Log_RestartDescriptor block is not equal to
        // the restart data length held in the Log_FileDescriptor block OR
        // the first Log_RestartDescriptor block is not equal to the second
        //   Return LOG_CORRUPTED

        if( logRD.restartValid != restartOffset ||
            logRD.restartDataLength != restartDataLength ||
            !logRD.equals(logRDEnd) )
            throw new LogException(null,LogException.LOG_CORRUPTED,7);

        // Copy the restart data length from Log_RestartDescriptor block into
        // the callers recordLengthP parameter
        // Return LOG_SUCCESS

        return readVect[1];
    }

    /**Closes (and optionally deletes) the log file.
     *
     * @param deleteFile Indicates whether file should be deleted.
     *
     * @return
     *
     * @exception LogException The close failed.
     *
     * @see
     */
    synchronized void closeFile( boolean deleteFile ) throws LogException {
        // Check BlockValid field in Log_FileDescriptor block and
        // ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // Set the block valid to NULL

        blockValid = null;

        // LOOP for each of the 16 elements in the log file's extent hash table

        boolean forced = false;
        Enumeration extents = extentTable.elements();
        while( extents.hasMoreElements() ) {
            LogExtent logEDP = (LogExtent)extents.nextElement();


            // IF extent has been written since last force
            //   Issue FSYNC for the extent's file descriptor
            //   IF not successful
            //     Return LOG_WRITE_FAILURE

            if( logEDP.writtenSinceLastForce ) {
                logEDP.fileHandle.fileSync();
                logEDP.writtenSinceLastForce = false;
                forced = true;
            }

            // Issue a close for the extent file.
            // Allow any error to pass to the caller.

            logEDP.fileHandle.fileClose();

            // If deletion of the logfile was requested, delete it.
            //Start IASRI 4720539
            if( deleteFile ){
                //if( !logEDP.file.delete() )
                final LogExtent tmplogEDP =  logEDP;
                Boolean isdeleted = (Boolean) java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction() {
                        public Object run(){
                            return tmplogEDP.file.delete();
                        }
                    }
                );
                if(!isdeleted.booleanValue())
                    throw new LogException(null,LogException.LOG_CLOSE_FAILURE,6);

            }
            //End IASRI 4720539
            // Address next block in chain
            // Clear the signature in the Log_ExtentDescriptor block
            // Deallocate the Log_ExtentDescriptor block

            extentTable.remove(logEDP.extentNumber);
            logEDP.doFinalize();
        }

        // IF any log extents were forced (FSYNC'ed)
        //   WRITE the Log_ControlDescriptor block to the control file (with
        //   implied sync)
        //   IF not successful allow the error to pass to the caller.
        //     Return LOG_WRITE_FAILURE

        if( forced && !logControl.logReadOnly )
            writeControlFile();

        // Issue CLOSE for the control file
        // IF not successful allow the error to pass to the caller.

        logFileHandle.fileClose();
        // logFileHandle.destroy();

        // If deletion of the logfile was requested, delete it's
        // control File and the cushion file.

        if( deleteFile ) {

            // Delete the control file.
            // Start IASRI 4720539
            //if( !logControl.controlFile.delete() )
            Boolean isdeleted = (Boolean) java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    public Object run(){
                        return logControl.controlFile.delete();
                    }
                }
            );
            if( !isdeleted.booleanValue() )
                throw new LogException(null,LogException.LOG_CLOSE_FAILURE,7);
            // End  IASRI 4720539
            freeCushion();

            // Finally remove the directory.
            // Start IASRI 4720539
            //LogControl.directory(logFileName,logControl.directoryPath).delete();
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    public Object run(){
                        return LogControl.directory(logFileName,logControl.directoryPath).delete();
                    }
                }
            );
            // End IASRI 4720539
        }

        // Unchain the Log_FileDescriptor block from the RCA chain
        // the latch will be unset and terminated by Log_RemoveFileDescriptor

        logControl.removeFile(this);

    }

    /**Truncates the log at the given point.
     *
     * @param truncLSN  The LSN of the truncation point.
     * @param inclusive Indicates whether truncation includes the LSN.
     *
     * @return
     *
     * @exception LogException  The operation failed.
     *
     * @see
     */
    synchronized void truncate( LogLSN truncLSN,
                                int    inclusive )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block and
        // ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // IF ReadOnly log
        //   Return LOG_READ_ONLY_ACCESS

        if( logControl.logReadOnly )
            throw new LogException(null,LogException.LOG_READ_ONLY_ACCESS,3);

        // IF the log file is empty (head LSN = LOG_NULL_LSN) &&
        // the lsn value specified is not equal to LOG_HEAD_LSN
        //   Unlock the Log_FileDescriptor latch
        //   Return LOG_NEW_TAIL_TOO_HIGH

        if( logControlDescriptor.headLSN.isNULL() ) {
            if( truncLSN.equals(LogLSN.HEAD_LSN) ) {
                return;
            } else
                throw new LogException(null,LogException.LOG_NEW_TAIL_TOO_HIGH,6);
        }

        // IF the lsn parameter is equal to the symbolic LOG_HEAD_LSN or
        // the lsn parameter is equal to the actual log head LSN
        //   Copy head LSN from Log_FileDescriptor into lsn
        //   Remember that head of log is being truncated
        // ELSE IF the lsn parameter is equal to LOG_TAIL_LSN
        //   Copy tail LSN from Log_FileDescriptor into lsn
        // ELSE Copy lsn parameter into lsn

        LogLSN lsn;
        boolean truncateHead = false;

        if( truncLSN.equals(LogLSN.HEAD_LSN) ||
            truncLSN.equals(logControlDescriptor.headLSN) ) {
            lsn = new LogLSN(logControlDescriptor.headLSN);
            truncateHead = true;
        } else if( truncLSN.equals(LogLSN.TAIL_LSN) )
            lsn = new LogLSN(logControlDescriptor.tailLSN);
        else
            lsn = new LogLSN(truncLSN);

        // Check the lsn parameter to ensure it is within the range of log records
        // IF lsn < log tail LSN (in Log_FileDescriptor)
        //   Unlock the Log_FileDescriptor latch
        //   Return LOG_NEW_TAIL_TOO_LOW
        // ELSE
        //   IF lsn > log head LSN
        //     Unlock the Log_FileDescriptor latch
        //     Return LOG_NEW_TAIL_TOO_HIGH

        if( lsn.lessThan(logControlDescriptor.tailLSN) )
            throw new LogException(null,LogException.LOG_NEW_TAIL_TOO_LOW,7);
        else if( lsn.greaterThan(logControlDescriptor.headLSN) )
            throw new LogException(null,LogException.LOG_NEW_TAIL_TOO_HIGH,8);

        // IF log head is being truncated &&
        // inclusive parameter = LOG_TAIL_NOT_INCLUSIVE
        //   Set Truncation record to the lsn specified (head LSN)
        //   and the New Tail LSN to the next lsn;
        // ELSE
        //   set truncation record and new log tail LSN depending
        //   on whether or not LOG_TAIL_INCLUSIVE was set. Either way the
        //   record pointed to by the current lsn must be read first

        LogLSN truncationRecord;
        LogLSN newTailRecord;
        boolean truncLastExtent = false;

        if( truncateHead &&
            inclusive == TAIL_NOT_INCLUSIVE ) {
            truncationRecord = new LogLSN(lsn);
            newTailRecord = new LogLSN(logControlDescriptor.nextLSN);
        } else {

            // IF inclusive parameter = LOG_TAIL_INCLUSIVE and
            // lsn parameter = log tail LSN (in Log_FileDescriptor)
            // (then there is nothing to truncate)
            //   Unlock the Log_FileDescriptor latch
            //   Return LOG_SUCCESS

            if( inclusive == TAIL_INCLUSIVE &&
                lsn.equals(logControlDescriptor.tailLSN) ) {
                return;
            }

            // Call Log_PositionFilePointer to position file pointer at the
            // start of the record specified by the lsn parameter
            // Allow any error to pass to the caller.

            LogExtent logEDP = positionFilePointer(lsn,0,LogExtent.ACCESSTYPE_READ);

            // Issue READ for the log record header
            // IF not successful return LOG_READ_ERROR

            byte[] headerBytes = new byte[LogRecordHeader.SIZEOF];
            int bytesRead = 0;

            try {
                bytesRead = logEDP.fileHandle.fileRead(headerBytes);
            } catch (LogException le) {
                logEDP.lastAccess = LogExtent.ACCESSTYPE_UNKNOWN;
                throw new LogException(LogException.LOG_READ_FAILURE, 11,
                        sm.getString("jts.log_read_header_failed"), le);
            }

            logEDP.cursorPosition += bytesRead;
            LogRecordHeader recordHeader = new LogRecordHeader(headerBytes,0);

            // Check that retrieved record is not an extent link record
            // IF it is
            //   Unlock the Log_FileDescriptor latch
            //   Return LOG_INVALID_TAIL

            if( recordHeader.recordType == LINK )
                throw new LogException(null,LogException.LOG_INVALID_TAIL,12);

            // Now set truncation record, and new tail LSN according to whether
            // or not LOG_TAIL_INCLUSIVE was specified

            if( inclusive == TAIL_INCLUSIVE ) {
                // The specified LSN is to be retained in the logfile so
                // set the truncation record to the previous LSN and the
                // new tail to the specified LSN

                truncationRecord = new LogLSN(recordHeader.previousLSN);
                newTailRecord = new LogLSN(lsn);

                // IF the current LSN is the first record in an extent file
                // Remember that previous extent file is to be truncated

                if( lsn.offset == 0 )
                    truncLastExtent = true;
            } else {

                // The specified LSN is to be truncated from the logfile so
                // set the truncation record to the specified LSN and the
                // new tail to the next LSN

                truncationRecord = new LogLSN(lsn);
                newTailRecord = new LogLSN(recordHeader.nextLSN);
            }
        }

        // Now that the true truncation point in the log file is known, work out
        // how many extent files (if any) can be unlinked
        // - Set first_extent to extent number from log tail LSN
        // - Set last_extent to extent number from truncation point LSN

        int firstExtent = logControlDescriptor.tailLSN.extent;
        int lastExtent = truncationRecord.extent;

        // IF log head is being truncated &&
        // inclusive parameter = LOG_TAIL_NOT_INCLUSIVE
        //   Set log tail LSN to current log head LSN
        //   Set log head LSN in Log_ControlDescriptor structure to LOG_NULL_LSN
        // ELSE
        //   Set log tail LSN in Log_ControlDescriptor structure
        //   to truncation point LSN

        if( truncateHead &&
            inclusive == TAIL_NOT_INCLUSIVE ) {
            logControlDescriptor.tailLSN.copy(newTailRecord);
            logControlDescriptor.headLSN.copy(LogLSN.NULL_LSN);
        } else
            logControlDescriptor.tailLSN.copy(newTailRecord);

        // Write (and implicitly sync) the Log_ControlDescriptor structure
        // to the control file.  Allow any error to pass to the caller.

        writeControlFile();

        // Now unlink any extent files no longer required
        // This involves processing each of the extent files in the range
        // FirstExtent to LastExtent-1.
        // Note: If the TruncationRecord is a link record (last in the extent
        // file), then the LastExtent must also be processed.

        if( truncLastExtent )
            lastExtent++;

        for( int extent = firstExtent; extent <= lastExtent-1; extent++ ) {

            // IF extent is currently open
            // Issue CLOSE for extent file
            // IF not successful allow the error to pass to the caller.

            LogExtent logEDP = (LogExtent)extentTable.get(extent);
            if( logEDP != null ) {
                logEDP.fileHandle.fileClose();
            }
            // Issue UNLINK for extent file
            // IF not successful
            //   Return LOG_CLOSE_FAILURE

            //Start IASRI 4720539
            //if( !logEDP.file.delete() )
            final LogExtent tmplogEDP = logEDP;
            Boolean isdeleted = (Boolean) java.security.AccessController.doPrivileged(
                    (PrivilegedAction) () -> tmplogEDP.file.delete()
            );
            if(!isdeleted)
                throw new LogException(null,LogException.LOG_CLOSE_FAILURE,15);
            //End IASRI 4720539
            // Unchain the Log_ExtentDescriptor block, set its BlockValid
            // field to binary zeroes and deallocate it.

            extentTable.remove(extent);
            if ( logEDP != null ) {
                logEDP.doFinalize();
            }
        }

        // If the cushion file does not exist and at least one extents has
        // just been removed, now is a good time to try and restore the
        // cushion file.
        // Call RestoreCushion but specify that the upcall should not
        // be called if the restore fails, as it should already have
        // been called when the cushion was first freed.

        if( !cushionExists &&
            firstExtent <= lastExtent - 1 )
            restoreCushion(false);

        // Call the platform specific SUPOS_LOG_FREE_FILE_STORAGE macro to
        // release any unwanted areas of the extent file containing the TAIL LSN
        // Allow any error to pass to the caller.

        if( logControlDescriptor.tailLSN.offset > 0 )
            freeFileStorage(logControlDescriptor.tailLSN);

        // If the log head has been set to 00000000.00000000, then ensure that
        // the next record which is written to the log causes the log control
        // data to be forced.  Otherwise, should a crash occur AFTER writing the
        // record and BEFORE writing the control file, when the log is re-opened
        // during restart, it will be assumed that the log is empty and no
        // scanning for records 'beyond the end of the log' will take place.

        if( logControlDescriptor.headLSN.isNULL() )
            recordsWritten = CONTROL_FORCE_INTERVAL;

    }

    /**Ensures that the log is written up to the given point.
     *
     * @param chkLSN  The last LSN which must be written.
     *
     * @return
     *
     * @exception LogException  The operation failed.
     *
     * @see
     */
    synchronized void checkLSN( LogLSN chkLSN )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by logHandle parameter, and ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // IF ReadOnly log
        //   Return LOG_READ_ONLY_ACCESS

        if( logControl.logReadOnly )
            throw new LogException(null,LogException.LOG_READ_ONLY_ACCESS,3);

        // IF the lsn parameter is equal to LOG_HEAD_LSN
        //   Copy head LSN from Log_FileDescriptor into lsn
        // ELSE IF the lsn parameter is equal to LOG_TAIL_LSN
        //   Copy tail LSN from Log_FileDescriptor into lsn
        // ELSE Copy lsn parameter into lsn

        LogLSN lsn;

        if( chkLSN.equals(LogLSN.HEAD_LSN) )
            lsn = new LogLSN(logControlDescriptor.headLSN);
        else if( chkLSN.equals(LogLSN.TAIL_LSN) )
            lsn = new LogLSN(logControlDescriptor.tailLSN);
        else
            lsn = new LogLSN(chkLSN);

        // IF lsn value is less than log tail LSN
        //   Return LOG_SUCCESS

        if( lsn.lessThan(logControlDescriptor.tailLSN) ) {
            return;
        }

        // IF log file is empty (log head = LOG_NULL_LSN)
        //   Unlock the Log_FileDescriptor latch
        //   Return LOG_SUCCESS

        if( logControlDescriptor.headLSN.isNULL() ) {
            return;
        }

        // IF lsn value is greater than log head LSN
        //   Copy head LSN from Log_FileDescriptor into lsn parameter

        if( lsn.greaterThan(logControlDescriptor.headLSN) )
            lsn.copy(logControlDescriptor.headLSN);

        // Determine the extent which contains the record to be forced (this is
        // derived from the 'extent' part of the lsn parameter) - remember this
        // as LAST_EXTENT

        int lastExtent = lsn.extent;

        // Determine the extent which contains the log tail LSN, remember this
        // as FIRST_EXTENT

        int firstExtent = logControlDescriptor.tailLSN.extent;

        // Now force each of the extent files (FIRST_EXTENT to LAST_EXTENT
        // inclusive)

        for( int extent = firstExtent; extent <= lastExtent; extent++ ) {

            // IF extent is currently open (Log_ExtentDescriptor block exists)
            //   IF the Written flag in the Log_ExtentDescriptor is TRUE
            //     Issue FSYNC for extent file
            //     IF not successful allow the error to pass to the caller.
            //     ELSE
            //       Set 'extent written' flag to FALSE

            LogExtent logEDP = (LogExtent)extentTable.get(extent);
            if( logEDP != null &&
                logEDP.writtenSinceLastForce ) {
                logEDP.fileHandle.fileSync();
                logEDP.writtenSinceLastForce = false;
            }
        }

        // IF 'extent' part of head LSN is same as LAST_EXTENT
        //   Force the Log_ControlDescriptor structure to the control file
        //   by issuing WRITE (implied sync)
        //   IF not successful allow the error to pass to the caller.
        // ELSE
        //   Don't force control data, since we do not want control data to
        //   be 'ahead' of extent data
        // The following block of code will no longer be executed as a result
        // of a performance suggestion.  This reduces the number of occasions
        // when the control data is forced to disk

        /*
          if( logControlDescriptor.headLSN.extent == lastExtent )
          writeControlFile();
        */

    }

    /**Opens a cursor on the log file.
     *
     * @param startLSN The start of the browse.
     * @param endLSN   The end of the browse.
     *
     * @return  The new cursor.
     *
     * @exception LogException The browse was not possible.
     *
     * @see
     */
    synchronized LogCursor openCursor( LogLSN startLSN,
                                       LogLSN endLSN )
        throws LogException {

        // Check BlockValid field in Log_FileDescriptor block and
        // ensure it is valid
        // IF not valid Log_FileDescriptor
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,2);

        // Allocate a Log_CursorDescriptor block
        // IF allocate fails
        //   Return LOG_INSUFFICIENT_MEMORY

        LogCursor cursor = new LogCursor(logControl,this,startLSN,endLSN);
        if( cursor == null ) {
            throw new LogException(null,LogException.LOG_INSUFFICIENT_MEMORY,4);
        }


        // Add the Log_CursorDescriptor block to the chain of similar blocks
        // hung off the LogFileDescriptor (anchor is CursorDescriptorHead)

        cursors.add(cursor);

        return cursor;
    }

    /**Closes the cursor.
     *
     * @param LogCursor The cursor to be closed.
     *
     * @return
     *
     * @exception LogException The cursor could not be closed.
     *
     * @see
     */
    synchronized void closeCursor( LogCursor cursor )
        throws LogException {

        // Check BlockValid field in Log_CursorDescriptor block and
        // ensure it is valid
        // IF not valid Log_CursorDescriptor
        //   Return LOG_INVALID_CURSOR

        if( cursor == null || cursor.blockValid != cursor )
            throw new LogException(null,LogException.LOG_INVALID_CURSOR,1);

        // Check BlockValid field in Log_FileDescriptor block pointed to
        // by field in Log_CursorDescriptor block and ensure it is valid

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_CURSOR,2);

        // IF not LogInitialised
        //   Return LOG_NOT_INITIALISED

        if( !logControl.logInitialised )
            throw new LogException(null,LogException.LOG_NOT_INITIALISED,3);

        // Now we know the blocks are valid.
        // Remove the Log_CursorDescriptor block from the chain hung off
        // the Log_FileDescriptor (CursorDescriptorHead)

        cursors.remove(cursor);
    }

    /**Positions the file pointer to the given position in the log.
     * This internal method does not need to be synchronized.
     *
     * @param lsn         The base position sought.
     * @param extra       An extra offset for the position.
     * @param accessType  The type of access.
     *
     * @return  The extent containing the given position.
     *
     * @exception LogException The operation failed.
     *
     * @see
     */
    LogExtent positionFilePointer( LogLSN currentLSN,
                                   int    extra,
                                   int    accessType )
        throws LogException {

        boolean extentJustOpened = false;       // Remember open operation

        // Run the extent chain to see if extent file is already open

        LogExtent extent = (LogExtent)extentTable.get(currentLSN.extent);

        // Open the extent file if it was not found in the extent chain

        if( extent == null ) {

            // Open the new extent file

            extent = openExtent(currentLSN.extent);
            extentJustOpened = true;
        }

        // If the current cursor position for the extent is not in the
        // required position, seek to the correct position

        if( extent.cursorPosition != currentLSN.offset + extra ||
            extent.lastAccess != accessType ) {

            // lseek to the correct offset in the open extent file
            // if the last cursor position is unknown or the distance
            // of the seek from the start of the file is closer to the
            // required position than the current position do a seek
            // from the start of the file, otherwise do a seek from the
            // current position.

            int seekDist =  ((currentLSN.offset + extra) > extent.cursorPosition) ?
                (currentLSN.offset + extra - extent.cursorPosition)  :
                (extent.cursorPosition - currentLSN.offset - extra);

            try {
                if( extent.lastAccess == LogExtent.ACCESSTYPE_UNKNOWN ||
                    currentLSN.offset + extra < seekDist )
                    extent.fileHandle.fileSeek(currentLSN.offset+extra,LogFileHandle.SEEK_ABSOLUTE);
                else
                    extent.fileHandle.fileSeek(currentLSN.offset+extra-extent.cursorPosition,LogFileHandle.SEEK_RELATIVE);
            } catch( LogException le ) {
                if( extentJustOpened ) {
                    extentTable.remove(currentLSN.extent);
                    extent.doFinalize();
                }

                throw new LogException(LogException.LOG_READ_FAILURE,3, null, le);
            }

            extent.cursorPosition = currentLSN.offset + extra;
            extent.lastAccess = accessType;
        }

        // Return the file descriptor for the extent file


        return extent;
    }

    /**Frees the cushion file.
     * <p>
     * This internal method does not need to be synchronized.
     *
     * @param
     *
     * @return
     *
     * @see
     */
    private void freeCushion() {
        // If the cushion file exists, remove it.

        if( cushionExists ) {

            // Delete the cushion file.
            // Start IASRI 4720539
            //logControl.cushionFile.delete();
            java.security.AccessController.doPrivileged(
                new java.security.PrivilegedAction() {
                    public Object run(){
                        return logControl.cushionFile.delete();
                    }
                }
            );
            // End IASRI 4720539
            cushionExists = false;
        }

    }

    /**Restores the cushion file
     * <p>
     * This internal method does not need to be synchronized.
     *
     * @param callUpcall  Indicates whether the upcall should be called.
     *
     * @return
     *
     * @exception LogException The operation failed.
     *
     * @see
     */
    void restoreCushion( boolean callUpcall )
        throws LogException {

        // IF the LOG_CUSHION_SIZE > 0

        if( CUSHION_SIZE > 0 ) {

            // if the cushion file already exists, set the flag in the
            // File Descriptor block to say so.

            if( !logControl.cushionFile.exists() ) {
                LogFileHandle cushionFH;              // Cushion file descriptor
                int openOptions = LogFileHandle.OPEN_RDWR  |
                    LogFileHandle.OPEN_CREAT |
                    LogFileHandle.OPEN_SYNC;

                // Create an empty log file as a storage cushion
                // Issue OPEN request for $REGIONDIR/log/cushion
                // IF OPEN fails
                //   Call function whose address is stored in UpcallFunction,
                //   passing a reason value of LOG_CALLBACK_REASON_SOS
                //   Unlock the Log_ProcessSharedLock
                //   Return

                try {
                    cushionFH = new LogFileHandle(logControl.cushionFile,openOptions);
                } catch( LogException le ) {
                    if( callUpcall && !upcallInProgress ) {
                        upcallInProgress = true;
                        upcallTarget.upcall(CALLBACK_REASON_SOS);
                        upcallInProgress = false;
                    }
                    throw new LogException(LogException.LOG_OPEN_FAILURE,3, null, le);
                }

                // Use Log_AllocFileStorage to create a file the
                // size LOG_CUSHION_SIZE
                // IF Log_AllocFileStorage fails
                //   Call function whose address is stored in UpcallFunction,
                //   passing a reason value of LOG_CALLBACK_REASON_SOS
                //   CLOSE & Unlink the cushion file
                //   Unlock the Log_ProcessSharedLock
                //   Return

                try {
                    cushionFH.allocFileStorage(CUSHION_SIZE);
                } catch( LogException le ) {
                    cushionFH.destroy();
                    // Start IASRI 4720539
                    //logControl.cushionFile.delete();
                    java.security.AccessController.doPrivileged(
                        new java.security.PrivilegedAction() {
                            public Object run(){
                                return logControl.cushionFile.delete();
                            }
                        }
                    );
                    // End IASRI 4720539

                    if( callUpcall && !upcallInProgress ) {
                        upcallInProgress = true;
                        upcallTarget.upcall(CALLBACK_REASON_SOS);
                        upcallInProgress = false;
                    }
                    cushionExists = false;
                    throw new LogException(LogException.LOG_OPEN_FAILURE,4, null, le);
                }

                // CLOSE the cushion file

                cushionFH.destroy();
            }

            cushionExists = true;
        }

    }

    /**Writes the control file.
     * This internal method does not need to be synchronized.
     *
     * @param
     *
     * @return
     *
     * @exception LogException The write failed.
     *
     * @see
     */
    void writeControlFile() throws LogException {

        // BUGFIX (Ram J) This fixes the log corruption problem.
        // The log extents have to be forced every time control
        // information is written. If not, there is a chance that
        // the control information (particularly headLSN) will go
        // inconsistent. The extent log that the headLSN in controlFile
        // points to, may not exist in the extent log, if it is
        // not forced. So, if JTS crashes, during recovery/reconstruction
        // there will be no log in the extents corresponding to the
        // headLSN stored in the control file. The fix is to force
        // all the dirty extents, everytime the control information
        // is updated.
        Enumeration extents = extentTable.elements();
        while (extents.hasMoreElements()) {
            LogExtent nextEDP = (LogExtent) extents.nextElement();
            if (nextEDP.writtenSinceLastForce) {
                try {
                    nextEDP.fileHandle.fileSync();
                    nextEDP.writtenSinceLastForce = false;
                } catch (LogException le) {
                    throw new LogException(LogException.LOG_ERROR_FORCING_LOG, 14, null, le);
                }
            }
        }

        // Move the file pointer to the beginning of the control file

        logFileHandle.fileSeek(0,LogFileHandle.SEEK_ABSOLUTE);

        // Write out the control data to the control file

        byte[] controlBytes = new byte[LogControlDescriptor.SIZEOF];
        logControlDescriptor.toBytes(controlBytes,0);
        logFileHandle.fileWrite(controlBytes);

    }

    /**Opens the given extent.
     * <p>
     * This internal method does not need to be synchronized.
     *
     * @param extent  The extent to open.
     *
     * @return  The extent opened.
     *
     * @exception LogException The open failed.
     *
     * @see
     */
    LogExtent openExtent( int extent ) throws LogException {

        // Build the extent file name from the CurrentLSN parameter

        File extentFile = logControl.extentFile(logFileName,LogExtent.modExtent(extent));

        // Issue an OPEN request for the file
        // IF not successful (rc == -1 and not EOF)
        //   Return LOG_OPEN_FAILURE

        int openOptions = LogFileHandle.OPEN_RDWR | LogFileHandle.OPEN_CREAT;
        if( logControl.logReadOnly )
            openOptions = LogFileHandle.OPEN_RDONLY;

        LogFileHandle extentFH = new LogFileHandle(extentFile,openOptions);

        // Allocate a Log_ExtentDescriptor block and initialise it

        LogExtent logEDP = new LogExtent(extent,extentFH,extentFile);

        // Use the already hashed extent number to find the position in the
        // hash table and add it to the chain

        extentTable.put(extent,logEDP);
        logEDP.blockValid = logEDP;

        return logEDP;
    }

    /**Frees file storage for the file.
     * This internal method does not need to be synchronized.
     *
     * @param tailLSN The point from which storage is not required.
     *
     * @return
     *
     * @exception LogException The operation failed.
     *
     * @see
     */
    void freeFileStorage( LogLSN tailLSN )
        throws LogException {

        // Using the extent containing the tail LSN, calculate the number of
        // bytes up to but not including the log tail record
        // Zero this space by issuing an FCLEAR request for the extent file
        // IF not successful
        //   Unlock the Log_FileDescriptor mutex
        //   Return LOG_WRITE_FAILURE

        int bytesToClear = tailLSN.offset;
        if( bytesToClear == 0 ) {
            return;
        }

        // Build LSN which will cause Log_PositionFilePointer to
        // position the file pointer at the start of the extent file

        LogLSN startOfExtent = new LogLSN(tailLSN.extent,0);
        LogExtent logEDP = positionFilePointer(startOfExtent,0,LogExtent.ACCESSTYPE_UNKNOWN);

        // Write the change to permanent storage via an FSYNC request

        logEDP.fileHandle.fileSync();

    }

    /**Checks restart record information.
     * This internal method does not need to be synchronized.
     *
     * @param fileHandle     The handle of the file.
     * @param restartNumber  The restart number.
     * @param restartInfo    An array which will contain the length and timestamp.
     *
     * @return
     *
     * @exception LogException The operation failed.
     *
     * @see
     */
    static void checkRestart( LogFileHandle fileHandle,
                              int           restartNumber,
                              int[/*2*/]    restartInfo )
        throws LogException {

        // Initialise callers output parameters

        restartInfo[0] = 0;                     // length
        restartInfo[1] = 0;                     // time stamp

        // Calculate the offsets within control file for both restart records
        // Use LSEEK to move to the first record and issue a READ for its
        // Log_RestartDescriptor block

        byte[] restartBytes = new byte[LogRestartDescriptor.SIZEOF];

        int offset = restartPosition(restartNumber);
        fileHandle.fileSeek(offset,LogFileHandle.SEEK_ABSOLUTE);
        int bytesRead = fileHandle.fileRead(restartBytes);
        LogRestartDescriptor logRD = new LogRestartDescriptor(restartBytes,0);

        // IF the READ is successful and it return the restart data

        if( bytesRead > 0 ) {

            // Check that the RestartValid value in the
            // Log_RestartDescriptor block matches the record offset
            // IF it matches
            //   Use LSEEK to move to the end of the restart data and read
            //   in the matching Log_RestartDescriptor block

            if( logRD.restartValid == restartPosition(restartNumber) ) {
                fileHandle.fileSeek(logRD.restartDataLength,
                                    LogFileHandle.SEEK_RELATIVE);
                fileHandle.fileRead(restartBytes);
                LogRestartDescriptor logRDEnd = new LogRestartDescriptor(restartBytes,0);


                // Check that the two Log_RestartDescriptor blocks are the same
                // IF they are identical
                //   Assume the first version of restart data is valid
                //   Remember the timestamp contained in the block

                if( logRD.equals(logRDEnd) ) {
                    restartInfo[0] = logRD.restartDataLength;
                    restartInfo[1] = logRD.timeStamp;
                }
                else
                    throw new LogException(null,LogException.LOG_CORRUPTED,1);
            }
        }

    }

    /**Dumps the state of the object.
     *
     * @param
     *
     * @return
     *
     * @exception LogException  The operation failed.
     *
     * @see
     */
    void dump() throws LogException {
/**
        LogExtent logEDP;                       // Extent file descriptor
        LogCursor logCuDP;                      // ptr to cursor descriptor

        // Check that the LogHandle passed points to a genuine File
        // Descriptor block using the BlockValid field.
        // IF the block is not genuine
        //   Return LOG_INVALID_FILE_DESCRIPTOR

        if( blockValid != this )
            throw new LogException(null,LogException.LOG_INVALID_FILE_DESCRIPTOR,1);

        // LOOP for each of the elements in the log file's extent hash table

        Enumeration extents = extentTable.elements();
        while( extents.hasMoreElements() ) {
            logEDP = (LogExtent)extents.nextElement();
        }

        java.util.Iterator curs = cursors.iterator();
        while( curs.hasNext() ) {
            logCuDP = (LogCursor)curs.next();
        }
**/
    }

    /**Removes all extent information from the log file.
     * This internal method does not need to be synchronized.
     *
     * @param
     *
     * @return
     *
     * @see
     */
    void cleanUpExtents() {

        Enumeration extents = extentTable.elements();
        while( extents.hasMoreElements() ) {
            LogExtent logEDP = (LogExtent)extents.nextElement();
            extentTable.remove(logEDP.extentNumber);
            logEDP.doFinalize();
        }
        extentTable = null;

    }

    /**Returns the alternate restart number.
     *
     * @param restart  The current restart number.
     *
     * @return  The new restart number.
     *
     * @see
     */

    final static int alternateRestart( int restart) {
        return (restart == 1) ? 2 : 1;
    }

    /**Returns the offset of the specified restart number.
     *
     * @param restart  The restart number.
     *
     * @return  The restart position.
     *
     * @see
     */
    final static int restartPosition( int restart ) {
        return (restart == 1) ? RESTART_OFFSET_1 : RESTART_OFFSET_2;
    }

    /**Returns the log file name.
     *
     * @param
     *
     * @return  The log file name.
     *
     * @see
     */
    final String logFileName() {
        return logFileName;
    }
}
